#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define WORD uint16_t
#define DWORD uint32_t
#define LONG uint32_t
#define BYTE uint8_t

typedef struct
{
    WORD bfType;  //specifies the file type
    DWORD bfSize;  //specifies the size in bytes of the bitmap file
    WORD bfReserved1;  //reserved; must be 0
    WORD bfReserved2;  //reserved; must be 0
    DWORD bfOffBits;  //species the offset in bytes from the bitmapfileheader to the bitmap bits
} __attribute__((__packed__)) BITMAPFILEHEADER;

typedef struct
{
    DWORD biSize;  //specifies the number of bytes required by the struct
    LONG biWidth;  //specifies width in pixels
    LONG biHeight;  //species height in pixels
    WORD biPlanes; //specifies the number of color planes, must be 1
    WORD biBitCount; //specifies the number of bit per pixel
    DWORD biCompression;//spcifies the type of compression
    DWORD biSizeImage;  //size of image in bytes
    LONG biXPelsPerMeter;  //number of pixels per meter in x axis
    LONG biYPelsPerMeter;  //number of pixels per meter in y axis
    DWORD biClrUsed;  //number of colors used by th ebitmap
    DWORD biClrImportant;  //number of colors that are important
} __attribute__((__packed__)) BITMAPINFOHEADER;

BYTE *LoadBitmapFile24(char *filename, BITMAPINFOHEADER *bitmapInfoHeader)
{
    FILE *filePtr; //our file pointer
    BITMAPFILEHEADER bitmapFileHeader; //our bitmap file header
    BYTE *bitmapImage;  //store image data

    //open filename in read binary mode
    filePtr = fopen(filename,"rb");
    if (filePtr == NULL)
        return NULL;

    //read the bitmap file header
    fread(&bitmapFileHeader, sizeof(BITMAPFILEHEADER), 1, filePtr);

    //verify that this is a bmp file by check bitmap id
    if (bitmapFileHeader.bfType !=0x4D42)
    {
        fclose(filePtr);
        return NULL;
    }

    //read the bitmap info header
    fread(bitmapInfoHeader, sizeof(BITMAPINFOHEADER), 1, filePtr);

    //move file point to the begging of bitmap data
    fseek(filePtr, bitmapFileHeader.bfOffBits, SEEK_SET);

    if(bitmapInfoHeader->biSizeImage==0)
    {
        bitmapInfoHeader->biSizeImage = bitmapInfoHeader->biWidth*bitmapInfoHeader->biHeight*bitmapInfoHeader->biBitCount/8;
    }

    //allocate enough memory for the bitmap image data
    bitmapImage = (BYTE *)malloc(bitmapInfoHeader->biSizeImage);

    //verify memory allocation
    if (!bitmapImage)
    {
        fclose(filePtr);
        return NULL;
    }

    //read in the bitmap image data
    fread(bitmapImage,1,bitmapInfoHeader->biSizeImage,filePtr);

    //close file and return bitmap image data
    fclose(filePtr);
    return bitmapImage;
}

int bmpFlip24(BYTE *bmpData, BITMAPINFOHEADER *header)
{
    int i;  //image index counter
    BYTE tempRGB;  //our swap variable

/*
    BYTE *line;
    unsigned long lineSize = header->biWidth*header->biBitCount/8;

    line = (BYTE *)malloc(lineSize); //temporary buffer for a line
    if(!line) return 0;

    BYTE *ptr1 = bmpData;
    BYTE *ptr2 = bmpData + header->biSizeImage - lineSize;
    for(i=0; i<header->biHeight; i++)
    {
        memcpy(line, ptr1, lineSize);
        memcpy(ptr1, ptr2, lineSize);
        memcpy(ptr2, line, lineSize);
        ptr1 += lineSize;
        ptr2 -= lineSize;
        if(ptr1>=ptr2) break;
    }

    free(line);
*/
    unsigned long lineSize = header->biWidth*header->biBitCount/8;
    BYTE *ptr1 = bmpData;
    BYTE *ptr2 = bmpData + header->biSizeImage - lineSize;

    for(i=0; i<header->biHeight; i++)
    {
        int j;
        for(j=0; j<lineSize; j++)
        {
          BYTE tmp = ptr1[j];
          ptr1[j] = ptr2[j];
          ptr2[j] = tmp;
        }
        ptr1 += lineSize;
        ptr2 -= lineSize;
        if(ptr1>=ptr2) break;
    }

    //swap the r and b values to get RGB (bitmap is BGR)
    for (i=0; i<header->biSizeImage;i+=3)
    {
        tempRGB = bmpData[i];
        bmpData[i] = bmpData[i + 2];
        bmpData[i + 2] = tempRGB;
    }

    return 1;
}

//RGB 565
BYTE *bmp24to16RGB(BYTE *bmpData, BITMAPINFOHEADER *header)
{
#define RGB16_SHF_R 0
#define RGB16_SHF_G 5
#define RGB16_SHF_B 11

    BYTE *newData = (BYTE *)malloc(header->biWidth*header->biHeight*2);
    if(!newData) return 0;

    int i;
    WORD *dst = (WORD *)newData;
    for(i=0; i<header->biSizeImage; i+=3)
    {
        BYTE r = (bmpData[i]>>3);
        BYTE g = (bmpData[i+1]>>2);
        BYTE b = (bmpData[i+2]>>3);

        WORD pixelRGB16 = (r<<RGB16_SHF_R) | (g<<RGB16_SHF_G) | (b<<RGB16_SHF_B);
        *dst = pixelRGB16;
        ++dst;
    }

    return newData;
}

//RGBA 1555
BYTE *bmp24to16RGBA(BYTE *bmpData, BITMAPINFOHEADER *header, BYTE tr, BYTE tg, BYTE tb)
{
#define RGBA16_SHF_R 0
#define RGBA16_SHF_G 5
#define RGBA16_SHF_B 10
#define RGBA16_OPAQUE 0x8000

    BYTE *newData = (BYTE *)malloc(header->biWidth*header->biHeight*2);
    if(!newData) return 0;

    int i;
    WORD *dst = (WORD *)newData;
    for(i=0; i<header->biSizeImage; i+=3)
    {
        BYTE r = bmpData[i];
        BYTE g = bmpData[i+1];
        BYTE b = bmpData[i+2];

        WORD pixelRGBA16;

        if( (r==tr) && (g==tg) && (b==tb) )
            pixelRGBA16 = 0;
        else
            pixelRGBA16 = RGBA16_OPAQUE;


        r >>= 3;
        g >>= 3;
        b >>= 3;

        pixelRGBA16 |= (r<<RGBA16_SHF_R) | (g<<RGBA16_SHF_G) | (b<<RGBA16_SHF_B);

        *dst = pixelRGBA16;
        ++dst;
    }

    return newData;
}

BYTE *raw16bppNxNto32x32(BYTE *data, int w, int h)
{
    BYTE *newData = (BYTE *)malloc(w*h*2);
    BYTE *dest = newData;
    int lineSize = w*2;
    int i,j,k;

    for(i=0; i<h; i+=32)
    {
        for(j=0; j<w; j+=32)
        {
            BYTE *line = data+j*2;
            for(k=0; k<32; k++)
            {
                memcpy(dest,line,32*2);
                dest += 32*2;
                line += lineSize;
            }
        }
        data += 32*lineSize;
    }

    return newData;
}

void saveRawFile(char *fname, BYTE *data, int dataLength)
{
    FILE *f = fopen(fname, "wb");

    fwrite(data, 1, dataLength, f);

    fclose(f);
}

void main(void)
{
    BITMAPINFOHEADER bitmapInfoHeader;
    unsigned char *bitmapData;

    bitmapData = LoadBitmapFile24("zelda.bmp",&bitmapInfoHeader);
    int flipped = bmpFlip24(bitmapData,&bitmapInfoHeader);

    BYTE *raw16 = bmp24to16RGBA(bitmapData, &bitmapInfoHeader, 255, 0, 0);

    BYTE *raw32x32 = raw16bppNxNto32x32(raw16, bitmapInfoHeader.biWidth,bitmapInfoHeader.biHeight);

    saveRawFile("lena.bin",raw32x32,bitmapInfoHeader.biWidth*bitmapInfoHeader.biHeight*2);
}

